package nachos.threads;

import nachos.machine.*;

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>, and multiple
 * threads can be waiting to <i>listen</i>. But there should never be a time
 * when both a speaker and a listener are waiting, because the two threads can
 * be paired off at this point.
 */
public class Communicator {
	/**
	 * Allocate a new communicator.
	 */
	public Communicator() {
		lock = new Lock();
		boolActiveSpeakers = false;
		boolActiveListeners = false;
		waitingSpeakers = new Condition(lock);
		waitingListeners = new Condition(lock);
		activeSpeakers = new Condition(lock);
		activeListeners = new Condition(lock);
	}

	/**
	 * Wait for a thread to listen through this communicator, and then transfer
	 * <i>word</i> to the listener.
	 * 
	 * <p>
	 * Does not return until this thread is paired up with a listening thread.
	 * Exactly one listener should receive <i>word</i>.
	 * 
	 * @param word the integer to transfer.
	 */
	public void speak(int word) {
		Lib.debug(dbgPeter, "[+][Communicator] speaking() in Communicator called!");
		lock.acquire();
		
		// Critical Section Start
		while(boolActiveSpeakers)
			waitingSpeakers.sleep();	// Critical Section End when this line executes

		boolActiveSpeakers = true;
		
		// Send the message
		this.word = word;
		
		if(boolActiveListeners == false)
		{
			waitingListeners.wake();
			activeSpeakers.sleep();	// Speaker(this line) -> Listener(will awake this) -> Speaker(wake for the termination)
			// Sleep and wait the listener to terminate the speaker for the pair

			// Finally this routine will terminates a pair
			boolActiveListeners = false;
			boolActiveSpeakers = false;
			waitingSpeakers.wake();
		}
		else if(boolActiveListeners == true)
			activeListeners.wake();	// Listener(sleep) -> Speaker(this line) -> Listener(wake for the termination)

		lock.release();
		Lib.debug(dbgPeter, "[+][Communicator] speaking() in Communicator finished!");
		return;
	}

	/**
	 * Wait for a thread to speak through this communicator, and then return the
	 * <i>word</i> that thread passed to <tt>speak()</tt>.
	 * 
	 * @return the integer transferred.
	 */
	public int listen() {
		Lib.debug(dbgPeter, "[+][Communicator] listen() in Communicator called!");
		int ret_word = 0;
		lock.acquire();
		
		// Critical Section Start
		while(boolActiveListeners)
			waitingListeners.sleep();	// Critical Section End when this line executes
		
		boolActiveListeners = true;
		
		if(boolActiveSpeakers == false)
		{
			waitingSpeakers.wake();
			activeListeners.sleep();	// Listener(this line) -> Speaker(will awake this) -> Listener(wake for the termination)
			// Sleep and wait the speaker to terminate the listener for the pair
			
			ret_word = word;	// Copy to local variable from the shared variable! It must be in the local area!

			// Finally this routine will terminates a pair
			boolActiveSpeakers = false;
			boolActiveListeners = false;
			activeListeners.wake();
		}
		else if(boolActiveSpeakers == true)
		{
			ret_word = word;	// Copy to local variable from the shared variable! It must be in the local area!
			
			activeSpeakers.wake();	// Speaker(sleep) -> Listener(this line) -> Speaker(wake for the termination)
		}

		lock.release();
		Lib.debug(dbgPeter, "[+][Communicator] listen() in Communicator finished!");
		return ret_word;
	}
	
	private static final char dbgPeter = 'P';
	
	private Lock lock;
	private boolean boolActiveSpeakers;
	private boolean boolActiveListeners;
	private Condition waitingSpeakers;
	private Condition waitingListeners;
	private Condition activeSpeakers;
	private Condition activeListeners;
	private static int word;
}
